package com.tangzx.builder.encoder;

import java.io.UnsupportedEncodingException;

import macromedia.abc.BytecodeBuffer;
import macromedia.asc.embedding.avmplus.ActionBlockConstants;
import com.tangzx.builder.abc.AbcCPool;
import com.tangzx.builder.abc.AbcFile;
import com.tangzx.builder.abc.ClassInfo;
import com.tangzx.builder.abc.InstanceInfo;
import com.tangzx.builder.abc.Metadata;
import com.tangzx.builder.abc.Method;
import com.tangzx.builder.abc.Trait;
import com.tangzx.builder.abc.string.AbcString;
import com.tangzx.builder.abc.string.MultiName;
import com.tangzx.builder.abc.string.Namespace;
import com.tangzx.builder.abc.string.NamespaceSet;
import com.tangzx.builder.abc.string.StringVo;
import com.tangzx.builder.context.IAbcContext;

public class AbcEncoder {
	public AbcFile abcFile;
	public AbcCPool cpool;
	public IAbcContext abcContext;

	public AbcEncoder(AbcFile file, IAbcContext context) {
		this.abcFile = file;
		this.cpool = file.cpool;
		this.abcContext = context;
	}

	public void writeCpool(BytecodeBuffer buf) {
		writeInts(buf);
		writeUints(buf);
		writeDoubles(buf);
		writeStrings(buf);
		writeNS(buf);
		writeNSS(buf);
		writeMN(buf);
	}

	public void writeMethods(BytecodeBuffer buf) {
		Method[] methods = abcFile.methods;
		int methodEntries = methods.length;
		buf.writeU32(methodEntries);
		for (int i = 0; i < methodEntries; i++) {
			Method m = methods[i];
			int[] param_types = m.paramTypes;
			int param_count = param_types.length;
			buf.writeU32(param_count);
			buf.writeU32(m.returnType);
			for (int j = 0; j < param_count; j++)
				buf.writeU32(param_types[j]);
			buf.writeU32(m.nameIndex);
			buf.writeU8(m.flags);
			if ((m.flags & ActionBlockConstants.METHOD_HasOptional) != 0) {
				int optional_param_count = m.optionalParamKinds.length;
				buf.writeU32(optional_param_count);
				for (int j = 0; j < optional_param_count; ++j) {
					buf.writeU32(m.optionalParamTypes[j]);
					buf.writeU8(m.optionalParamKinds[j]);
				}
			}

			if ((m.flags & ActionBlockConstants.METHOD_HasParamNames) != 0) {
				int param_name_count = param_count;
				for (int j = 0; j < param_name_count; ++j) {
					buf.writeU32(m.paramNames[j]);
				}
			}
		}
	}

	public void writeMetadata(BytecodeBuffer buf) {
		Metadata[] raw_metadata = abcFile.metadata;
		int metadataEntries = raw_metadata.length;
		buf.writeU32(metadataEntries);
		for (int i = 0; i < metadataEntries; i++) {
			Metadata meta = raw_metadata[i];
			int count = meta.keys.length;
			buf.writeU32(meta.name);
			buf.writeU32(count);
			for (int j = 0; j < count; j++)
				buf.writeU32(meta.keys[j]);
			for (int j = 0; j < count; j++)
				buf.writeU32(meta.values[j]);
		}
	}

	public void writeClasses(BytecodeBuffer buf) {
		ClassInfo[] classInfos = abcFile.classInfos;
		InstanceInfo[] instanceInfos = abcFile.instanceInfos;
		int classEntries = classInfos.length;
		buf.writeU32(classEntries);
		// InstanceInfos
		for (int i = 0; i < classEntries; i++) {
			InstanceInfo instanceInfo = instanceInfos[i];
			buf.writeU32(instanceInfo.name_index);
			buf.writeU32(instanceInfo.super_index);
			buf.writeU8(instanceInfo.flags);
			if ((instanceInfo.flags & ActionBlockConstants.CLASS_FLAG_protected) != 0)
				buf.writeU32(instanceInfo.protected_ns);

			int interface_count = instanceInfo.interfaces.length;
			buf.writeU32(interface_count);
			for (int j = 0; j < interface_count; j++)
				buf.writeU32(instanceInfo.interfaces[j]);
			buf.writeU32(instanceInfo.init_index);
			writeTraits(instanceInfo.traits, buf);
		}

		// classes
		for (int i = 0; i < classEntries; i++) {
			ClassInfo ci = classInfos[i];
			buf.writeU32(ci.clinitIndex);
			writeTraits(ci.traits, buf);
		}
	}

	public void writeTraits(Trait[] traits, BytecodeBuffer buf) {
		int count = traits.length;
		buf.writeU32(count);
		for (int i = 0; i < traits.length; i++) {
			Trait trait = traits[i];
			buf.writeU32(trait.nameIndex);
			buf.writeU8(trait.kind);
			int tag = trait.getTag();
			switch (tag) {
			case ActionBlockConstants.TRAIT_Var:
			case ActionBlockConstants.TRAIT_Const:
				int slot_id = trait.data[0];
				int trait_type = trait.data[1];
				int value_index = trait.data[2];
				buf.writeU32(slot_id);
				buf.writeU32(trait_type);
				buf.writeU32(value_index);
				if (value_index > 0)
					buf.writeU8(trait.data[3]);
				break;
			case ActionBlockConstants.TRAIT_Method:
			case ActionBlockConstants.TRAIT_Getter:
			case ActionBlockConstants.TRAIT_Setter:
			case ActionBlockConstants.TRAIT_Class:
			case ActionBlockConstants.TRAIT_Function:
				buf.writeU32(trait.data[0]);
				buf.writeU32(trait.data[1]);
				break;
			default:
				break;
			}

			if (trait.hasMetadata()) {
				int metadata_count = trait.metadata.length;
				buf.writeU32(metadata_count);
				for (int mIndex : trait.metadata)
					buf.writeU32(mIndex);
			}
		}
	}

	protected void writeDoubles(BytecodeBuffer buf) {
		double[] doubles = cpool.getDoubleSet();
		int size = doubles.length;
		buf.writeU32(size);
		for (int i = 1; i < size; i++) {
			buf.writeDouble(doubles[i]);
		}
	}

	protected void writeUints(BytecodeBuffer buf) {
		long[] uints = cpool.getUintSet();
		int size = uints.length;
		buf.writeU32(size);
		for (int i = 1; i < size; i++) {
			buf.writeU32(uints[i]);
		}
	}

	protected void writeInts(BytecodeBuffer buf) {
		int[] ints = cpool.getIntSet();
		int size = ints.length;
		buf.writeU32(size);
		for (int i = 1; i < size; i++) {
			buf.writeU32(ints[i]);
		}
	}

	protected void writeStrings(BytecodeBuffer buf) {
		AbcString[] strings = cpool.getStringSet();
		int size = strings.length;
		buf.writeU32(size);
		for (int i = 1; i < size; i++) {
			String value = strings[i].toString();
			byte[] strByte = null;
			try {
				strByte = value.getBytes("UTF8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			int len = buf.size();
			buf.writeU32(strByte.length);
			buf.writeBytes(new BytecodeBuffer(strByte), 0, strByte.length);
			buf.skip(strByte.length);
		}
	}

	protected void writeNS(BytecodeBuffer buf) {
		Namespace[] namespaces = cpool.getNamespaceSet();
		buf.writeU32(namespaces.length);
		for (int i = 1; i < namespaces.length; i++) {
			Namespace ns = namespaces[i];
			buf.writeU8(ns.nsKind);
			buf.writeU32(ns.nameOffset);
		}
	}

	protected void writeNSS(BytecodeBuffer buf) {
		NamespaceSet[] namespaceSets = cpool.getNamespaceSetSet();
		int size = namespaceSets.length;
		buf.writeU32(size);
		for (int i = 1; i < size; i++) {
			NamespaceSet nss = namespaceSets[i];
			buf.writeU32(nss.size());
			for (int data : nss.ids) {
				buf.writeU32(data);
			}
		}
	}

	protected void writeMN(BytecodeBuffer buf) {
		MultiName[] multiNames = cpool.getMultiNameSet();
		int size = multiNames.length;
		buf.writeU32(size);
		for (int i = 1; i < size; i++) {
			MultiName mn = multiNames[i];
			int kind = mn.getKind();
			buf.writeU8(kind);
			switch (kind) {
			case ActionBlockConstants.CONSTANT_Qname:
			case ActionBlockConstants.CONSTANT_QnameA:
			case ActionBlockConstants.CONSTANT_RTQname:
			case ActionBlockConstants.CONSTANT_RTQnameA:
			case ActionBlockConstants.CONSTANT_Multiname:
			case ActionBlockConstants.CONSTANT_MultinameA:
			case ActionBlockConstants.CONSTANT_MultinameL:
			case ActionBlockConstants.CONSTANT_MultinameLA:
			case ActionBlockConstants.CONSTANT_TypeName:
				int[] params = mn.getParams();
				for (int p : params)
					buf.writeU32(p);
				break;
			case ActionBlockConstants.CONSTANT_RTQnameL:
			case ActionBlockConstants.CONSTANT_RTQnameLA:
				break;
			default:
				throw new RuntimeException("bad multiname type: " + kind);
			}
		}
	}

	public void resetAbcData() {
		BytecodeBuffer buf = new BytecodeBuffer(abcFile.data.abc.length);
		buf.writeU16(abcFile.minorVersion);
		buf.writeU16(abcFile.majorVersion);
		
		BytecodeBuffer buf2 = new BytecodeBuffer(abcFile.data.abc.length);
		writeMethods(buf2);
		writeMetadata(buf2);
		writeClasses(buf2);

		writeCpool(buf);
		
		buf.writeBytes(buf2, 0, buf2.size());
		byte[] bytes = abcFile.remain;
		buf.writeBytes(new BytecodeBuffer(bytes), 0, bytes.length);
		buf.seek(0);
		byte[] abcBytes = buf.readBytes(buf.size());
		abcFile.data.abc = abcBytes;
	}

	public void mixClassInfo() {
		ClassInfo[] classInfos = abcFile.classInfos;
		for (ClassInfo classInfo : classInfos) {
			mixTrait(classInfo.traits);
		}
	}

	public void mixNS() {
		Namespace[] namespaces = cpool.getNamespaceSet();
		for (int i = 1; i < namespaces.length; i++) {
			Namespace ns = namespaces[i];
			AbcString as = cpool.getString(ns.nameOffset);
			if (ns.nsKind == ActionBlockConstants.CONSTANT_PackageNamespace
					&& as.vo.isCanMix()) {
				// 包名直接在最外层
				as.vo.setValue("");
				as.vo.pushString(true);
			}
		}
	}

	public void mixClassName() {
		InstanceInfo[] ii = abcFile.instanceInfos;
		for (InstanceInfo instanceInfo : ii) {
			MultiName mn = cpool.getMultiName(instanceInfo.name_index);
			int[] params = mn.getParams();
			Namespace packageNS = cpool.getNamespace(params[0]);
			AbcString packageAS = cpool.getString(packageNS.nameOffset);
			AbcString classNameAS = cpool.getString(params[1]);

			if (abcContext.hasSameClassNameButNotSamePackage(packageAS, classNameAS)) {
				if (!packageAS.vo.isKind(StringVo.SAME_CLASS)) {
					if (packageAS.vo.isCanMix()) {
						packageAS.vo.setValue(abcContext.makeWord(packageAS.vo.getOriValue()));
					}
					packageAS.vo.setKind(StringVo.SAME_CLASS);
					packageAS.vo.pushString(true);
				}
			} else {
				
			}

			if (classNameAS.vo.isCanMix())
				classNameAS.vo.setValue(abcContext.makeWord(classNameAS.vo.getOriValue()));
			classNameAS.vo.pushString(true);
			classNameAS.vo.setKind(StringVo.CLASS_NAME);
			abcContext.registerClassName(packageAS, classNameAS);
		}
	}

	public void mixTrait(Trait[] all) {
		for (Trait trait : all) {
			MultiName mn = cpool.getMultiName(trait.nameIndex);
			AbcString as = cpool.getString(mn.params[1]);
			switch (trait.getTag()) {
			case Trait.Trait_Method:
			case Trait.Trait_Getter:
			case Trait.Trait_Setter:
			case Trait.Trait_Function:
				Namespace ns = cpool.getNamespace(mn.params[0]);
				if (as.vo.isCanMix()) {
					as.vo.setValue(abcContext.makeWord(as.vo.getOriValue()));
				}
				as.vo.pushString(true);
				break;
			case Trait.Trait_Slot:
			case Trait.Trait_Const:
				if (as.vo.isCanMix()) {
					as.vo.setValue(abcContext.makeWord(as.vo.getOriValue()));
				}
				as.vo.pushString(true);
				break;
			}
		}
	}

	public void mixField() {
		InstanceInfo[] ii = abcFile.instanceInfos;
		for (InstanceInfo instanceInfo : ii) {
			Trait[] all = instanceInfo.traits;
			for (Trait trait : all) {
				MultiName mn = cpool.getMultiName(trait.nameIndex);
				switch (trait.getTag()) {
				case Trait.Trait_Slot:
				case Trait.Trait_Const:
					AbcString as = cpool.getString(mn.params[1]);
					if (as.vo.isCanMix()) {
						//as.vo.setValue(as.vo.getOriValue() + "__");
						as.vo.setValue(abcContext.makeWord(as.vo.getOriValue()));
					}
					as.vo.pushString(true);
					break;
				}
			}
		}
	}

	public void mixMethod() {
		InstanceInfo[] ii = abcFile.instanceInfos;
		for (InstanceInfo instanceInfo : ii) {
			Trait[] all = instanceInfo.traits;
			for (Trait trait : all) {
				MultiName mn = cpool.getMultiName(trait.nameIndex);
				switch (trait.getTag()) {
				case Trait.Trait_Method:
				case Trait.Trait_Getter:
				case Trait.Trait_Setter:
				case Trait.Trait_Function:
					Namespace ns = cpool.getNamespace(mn.params[0]);
					AbcString as = cpool.getString(mn.params[1]);
					if (as.vo.isCanMix()) {
						/*
						String word = abcContext.makeWord();
						//private
						if (ns.nsKind == 0x05) {
						}
						*/
						as.vo.setValue(abcContext.makeWord(as.vo.getOriValue()));
					}
					as.vo.setCanNotMix();
					//as.vo.pushString(true);
					break;
				}
			}
		}
	}
}
